
from datetime import datetime
import numpy as np
import Agent.Astar.Astar_Home
import Config

'''
if Config.SHOWN is True:
    playboard.drawingboard()
'''
# env = deliveryManEnv(playboard.barrier_states, playboard.reward_states,
#                      play1state= playboard.state1, play2state = playboard.state2,
#                      play1score= playboard.play1score,play2score= playboard.play2score)
# node = Node(move = None,value = None,env=env,playturn = 2,deepth= 1,isTerminal = False,selfPlay = 1)
# action = alpha_beta_search(node)

# reset = False
#
# # 获取当前局势数据
# env = deliveryManEnv(playboard.barrier_states, reward_states=playboard.reward_states.copy(),
#                      play1state=playboard.state1, play2state=playboard.state2,
#                      play1score=playboard.play1score, play2score=playboard.play2score,
#                      final1Score=playboard.final1Score,final2Score=playboard.final2Score,
#                      play1jobs=playboard.play1capacity,play2jobs=playboard.play2capacity)
# # 获取环境数据
# x_data = env.toProgrammingBoard(player=1)


class ArrayMatrix:
    """
        说明：
            1.构造方法需要两个参数，即二维数组的宽和高
            2.成员变量w和h是二维数组的宽和高
            3.使用：‘对象[x][y]’可以直接取到相应的值
            4.数组的默认值都是0
    """

    def __init__(self, w, h):
        self.w = w
        self.h = h
        self.data = []
        self.data = [[0 for y in range(h)] for x in range(w)]

    def showArray2D(self):
        for y in range(self.h):
            for x in range(self.w):
                print(self.data[x][y], end=' ')
            print("")

    def __getitem__(self, item):
        return self.data[item]


class Point:
    """
    表示一个点
    """

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __eq__(self, other):
        if self.x == other.x and self.y == other.y:
            return True
        return False

    def __str__(self):
        return "x:" + str(self.x) + ",y:" + str(self.y)


# new_Astar
class NewAstar:
    """
    AStar算法的Python3.x实现
    """

    class Node:  # 描述AStar算法中的节点数据
        def __init__(self, point, endPoint, g=0):
            self.point = point  # 自己的坐标
            self.father = None  # 父节点
            self.g = g  # g值，g值在用到的时候会重新算
            self.h = (abs(endPoint.x - point.x) + abs(endPoint.y - point.y)) * 10  # 计算h值

    def __init__(self, map2d, startPoint, endPoint, passTag=0):
        """
        构造AStar算法的启动条件
        :param map2d: Array2D类型的寻路数组
        :param startPoint: Point类型的寻路起点
        :param endPoint: Point类型的寻路终点
        :param passTag: int类型的可行走标记（若地图数据!=passTag即为障碍）
        """
        # 开启表
        self.openList = []
        # 关闭表
        self.closeList = []
        # 寻路地图
        self.map2d = map2d
        # 起点终点
        self.startPoint = startPoint
        self.endPoint = endPoint
        # 可行走标记
        self.passTag = passTag

    def getMinNode(self):
        """
        获得openlist中F值最小的节点
        :return: Node
        """
        currentNode = self.openList[0]
        for node in self.openList:
            if node.g + node.h < currentNode.g + currentNode.h:
                currentNode = node
        return currentNode

    def pointInCloseList(self, point):
        for node in self.closeList:
            if node.point == point:
                return True
        return False

    def pointInOpenList(self, point):
        for node in self.openList:
            if node.point == point:
                return node
        return None

    def endPointInCloseList(self):
        for node in self.openList:
            if node.point == self.endPoint:
                return node
        return None

    def searchNear(self, minF, offsetX, offsetY):
        """
        搜索节点周围的点
        :param minF:
        :param offsetX:
        :param offsetY:
        :return:
        """
        # 越界检测
        if minF.point.x + offsetX < 0 or minF.point.x + offsetX > self.map2d.w - 1 or minF.point.y + offsetY < 0 or minF.point.y + offsetY > self.map2d.h - 1:
            return
        # 如果是障碍，就忽略
        if self.map2d[minF.point.x + offsetX][minF.point.y + offsetY] != self.passTag:
            return
        # 如果在关闭表中，就忽略
        if self.pointInCloseList(Point(minF.point.x + offsetX, minF.point.y + offsetY)):
            return
        # 设置单位花费
        if offsetX == 0 or offsetY == 0:
            step = 10
        else:
            step = 14
        # 如果不再openList中，就把它加入openlist
        currentNode = self.pointInOpenList(Point(minF.point.x + offsetX, minF.point.y + offsetY))
        if not currentNode:
            currentNode = NewAstar.Node(Point(minF.point.x + offsetX, minF.point.y + offsetY), self.endPoint,
                                     g=minF.g + step)
            currentNode.father = minF
            self.openList.append(currentNode)
            return
        # 如果在openList中，判断minF到当前点的G是否更小
        if minF.g + step < currentNode.g:  # 如果更小，就重新计算g值，并且改变father
            currentNode.g = minF + step
            currentNode.father = minF

    def start(self):
        '''
        开始寻路
        :return: None或Point列表（路径）
        '''
        # 1.将起点放入开启列表
        startNode = NewAstar.Node(self.startPoint, self.endPoint)
        self.openList.append(startNode)
        # 2.主循环逻辑
        while True:
            # 找到F值最小的点
            minF = self.getMinNode()
            # 把这个点加入closeList中，并且在openList中删除它
            self.closeList.append(minF)
            self.openList.remove(minF)
            # 判断这个节点的上下左右节点
            self.searchNear(minF, 0, -1)
            self.searchNear(minF, 0, 1)
            self.searchNear(minF, -1, 0)
            self.searchNear(minF, 1, 0)
            # 判断是否终止
            point = self.endPointInCloseList()
            if point:  # 如果终点在关闭表中，就返回结果
                # print("关闭表中")
                cPoint = point
                pathList = []
                while True:
                    if cPoint.father:
                        pathList.append(cPoint.point)
                        cPoint = cPoint.father
                    else:
                        # print(pathList)
                        # print(list(reversed(pathList)))
                        # print(pathList.reverse())
                        return list(reversed(pathList))
            if len(self.openList) == 0:
                return None


# 棋盘的矩离矩阵计算函数
def cal_distance_value(input_dict):
    result_matrix = np.zeros([len(input_dict), len(input_dict)])
    for x_i in range(1, len(input_dict)+1):
        for y_i in range(1, len(input_dict)+1):
            if x_i != y_i:
                s_point = input_dict[x_i]
                e_point = input_dict[y_i]
                map2d = ArrayMatrix(12, 12)
                astar = NewAstar(map2d, Point(s_point[0], s_point[1]), Point(e_point[0], e_point[1]))
                pathlist = astar.start()
                if pathlist is None:
                    result_matrix[x_i-1, y_i-1] = 1000
                else:
                    result_matrix[x_i-1, y_i-1] = len(pathlist)

    return result_matrix


# 定义一个函数来实现输入dict来组合所有可能的下一步
def second_point_continue(input_dict, input_list, dist_matrix, tdictx):
    init_dict = dict()
    # 获取每一个input_dict某一个元素对应的结果数值
    visit_points = list(input_dict.values())[0]
    # 判断当前循环状态下对应的已访问的长度
    # visit_points_list = visit_points[2]
    # print(visit_points_list)
    # print(input_list)
    # # 打印出当前已完成了几轮树搜索
    # print('当前已经完成了 %s 层路网搜索' % str(len(visit_points_list)))
    for x_i in list(input_dict.keys()):
        visited_points = input_dict[x_i][2]

        for y_i in input_list:
            distance_yi = dist_matrix[x_i-1, y_i-1]
            score_yi = tdictx[y_i][2]
            if y_i not in visited_points:
                distance_value = input_dict[x_i][0] + distance_yi
                score_value = input_dict[x_i][1] + score_yi
                templist = [visited_points[0], y_i]
                # 调用函数，将templist拆分至最小的要素单元
                init_dict[str(templist)] = [distance_value, score_value, templist]

    return init_dict


# 计算第三层的最短距离函数
# 定义一个函数来实现输入dict来组合所有可能的下一步
def three_point_continue(input_dict, input_list, input_dist, input_tdict):
    init_dict = dict()
    # 获取每一个input_dict某一个元素对应的结果数值
    visit_points = list(input_dict.values())[0]
    # 判断当前循环状态下对应的已访问的长度
    visit_points_list = visit_points[2]
    # print(visit_points_list)
    # print(input_list)
    # # 打印出当前已完成了几轮树搜索
    # print('当前已经完成了 %s 层路网搜索' % str(len(visit_points_list)))
    for x_i in list(input_dict.keys()):
        visited_points = input_dict[x_i][2]
        for y_i in input_list:
            if y_i not in visited_points:
                score_yi = input_tdict[y_i][2]
                score_value = input_dict[x_i][1] + score_yi
                templist = [visited_points[0], visited_points[1], y_i]
                distance_yi = input_dist[y_i-1, visited_points[1]-1]
                distance_value = input_dict[x_i][0] + distance_yi
                # 调用函数，将templist拆分至最小的要素单元
                init_dict[str(templist)] = [distance_value, score_value, templist]

    return init_dict


# 定义一个函数，计算收集全局三个包裹的步数即对应三个点的坐标信息
def search_local_best_path(input_data):
    [x, y] = input_data.shape
    b_dict = dict()
    t_dict = dict()
    b_num = 0
    t_num = 0
    start_point = list()

    # 记录全局所有的坐标信息
    for xi in range(x):
        for yi in range(y):
            if input_data[xi, yi] == 1:
                b_num = b_num + 1
                b_dict[b_num] = [xi, yi]
            elif input_data[xi, yi] > 2:
                t_num = t_num + 1
                t_dict[t_num] = [xi, yi, input_data[xi, yi]]
            elif input_data[xi, yi] == 2:
                start_point.append(xi)
                start_point.append(yi)

    t_number = len(t_dict)
    # 定义模型计算的时间
    start_time = datetime.now()

    # 定义一个矩阵，构建距离矩阵——自己的位置分值为0
    t_dict[t_number + 1] = [start_point[0], start_point[1], 0]

    # 计算目标点与起始点之间的距离矩阵
    distance_matrix = cal_distance_value(t_dict)

    # 标记的每一个包裹的i，可直接调用t_dict中的伴置坐标及对应的包裹分值
    point_list = list(range(1, t_number+1))
    # 起点即为t_number+1位置对应的包裹信息
    result_dict = dict()  # 记录最终的结果字典
    # 定义循环字典表，进行short_path的遍历，并记录所有的可能性

    # 完成了起点至第一层的最短距离与路径记录
    for point_x in point_list:
        visit_point_i = list()
        distance_i = distance_matrix[point_x-1, t_number]
        score_i = t_dict[point_x][2]
        visit_point_i.append(point_x)
        result_dict[point_x] = [distance_i, score_i, visit_point_i]

    # 计算第二层的最短距离与路径记录
    second_dict_test = second_point_continue(result_dict, point_list, distance_matrix, t_dict)

    # 计算第三层的最短距离与路径记录
    three_dict_test = three_point_continue(second_dict_test, point_list, distance_matrix, t_dict)

    # 计算三层搜索的最佳路线
    key_list = list(three_dict_test.keys())
    dis_list = list()
    s_list = list()
    score_list = list()
    path_list = list()

    for nn_i in key_list:
        selected_list = three_dict_test[nn_i]
        # 记录行走距离
        step_num = selected_list[0]
        score_num = selected_list[1]
        path_x = selected_list[2]
        compare_score = score_num * pow(step_num, -0.4)
        dis_list.append(step_num)
        s_list.append(score_num)
        score_list.append(compare_score)
        path_list.append(path_x)

    # 将score_list与path_list进行比较，然后找出最佳线路
    x_max = max(score_list)
    mm_index = score_list.index(x_max, 0)
    best_path = path_list[mm_index]
    shortest_num = dis_list[mm_index]
    # print(s_list[mm_index])
    # print(best_path)
    l_list_1 = t_dict[best_path[0]][0:2]
    l_list_2 = t_dict[best_path[1]][0:2]
    l_list_3 = t_dict[best_path[2]][0:2]
    location_list = [l_list_1, l_list_2, l_list_3]

    # 确定相应的距离矩阵
    time_spend = datetime.now() - start_time

    print('the calculating time is %s' % str(time_spend))

    return [shortest_num, location_list]

def return_search_local_result(env,player):

    inputdata = env.toProgrammingBoard(player = player)
    [shortest_num, location_list] = search_local_best_path(inputdata)

    location_list_result = []
    for number in location_list:
        result = number[0] * Config.CHECKERBOARDSIZE + number[1]
        location_list_result.append(result)


    return location_list_result


def return_local_action(nowstate,env,actionlist,player = 0):
    target = actionlist[0]
    actionlist.remove(target)
    astar_home = Agent.Astar.Astar_Home.AStar(startpoint=nowstate, endpoint=target, env=env, player=player)
    action,_ = astar_home.action()
    return action
# [short_dist, path_location] = search_local_best_path(x_data)
# print(short_dist)
# print(path_location)
